﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Timing;

namespace Acme.Abp.SerialNumberGenerator;

/// <summary>
/// 序号生成器
/// </summary>
/// <remarks>
/// 通用说明：
/// 时间格式字符串：unix表示将时间格式化为unix时间戳，单位为秒。unixMilli表示将时间格式化为unix时间戳，单位毫秒。 yyyy-MM-dd hh:mm:ss表示将时间格式化为2020-01-20 15:59:30的形式。其中yyyy表示年份，MM表示月份，dd表示天。hh表示小时，mm表示分钟，ss表示秒。
/// 示例:yyyy/MM/dd将时间格式化为2020/01/20的形式。
/// ${number@rand@length}：number、rand为固定字符串,length为大于1小于1000的整数。 产生随机数字字符串。
/// 示例：${number@rand@5} 产生长度为5的随机数字组成的字符串。
/// ${numberRange@rand@min@max@precision}：numberRange、rand为固定字符串，min为数值下限，支持整数和浮点数。 max为数值上限，支持整数和浮点数。precision为小数点后保留的位数，0即为整数，大于0即为特定小数位数的浮点数。 产生特定范围的随机数，随机数支持正数，负数，整数，浮点数。
/// 示例：${numberRange@rand@-1.237@3.989@4} 产生大于-1.237且小于3.989的浮点数，小数点后保留4为小数。
///     ${number@start@step@padding}：number为固定字符串,start为起始整数,step为步长(步长可以为正整数或者负整数), padding的值为true或者false,表示是否补0。 产生递增或者递减数字。
/// 示例：${number@0@1@true} 产生从0开始递增为1的序列号，并且前置补0对齐。
///     ${lower@rand@length}：lower、rand为固定字符串，length为产生的随机字符串长度。产生随机小写字符串。
/// 示例：${lower@rand@5} 产生长度为5的随机小写英文字母字符串。
///     ${upper@rand@length}：upper、rand为固定字符串，length为产生的随机字符串长度。产生随机大写字符串。
/// 示例：${upper@rand@5} 产生长度为5的随机大写英文字母字符串。
///     ${alphaNumber@rand@length}：alphaNumber、rand为固定字符串，length为随机字符串长度。产生随机大写、小写、数字字符串。
/// 示例：${alphaNumber@rand@5} 产生长度为5的随机小写、大写、数字组成的字符串。
///     ${time@format@start@stepSecond}：使用时间参与序列号生成。time为固定字符串。format为时间格式化字符串。 start为起始时间,格式固定为yyyy-MM-dd hh:mm:ss的形式。 stepSecond为时间变化的步长(可以为正整数或者负整数)，单位为秒。
/// 示例：${time@yyyy-MM-dd hh:mm@2020-01-01 12:00:00@300} 产生从2020-01-01 12:00:00开始，自增5分钟的时间字符串。 产生的时间字符串的格式为yyyy-MM-dd hh:mm。
///     ${unicode@start@end@length}：unicode为固定字符串。start为起始unicode字符编码,长度为4的十六进制字符串(两个字节)。 end为截止unicode字符编码，长度为4的十六进制字符串(两个字节)。 end需要大于start。length为产生随机unicode字符串的长度。产生范围内的随机unicode字符串。
/// 示例：${unicode@0041@005A@5} 产生长度为5的随机大写英文字母。${unicode@4E00@9FA5@5} 产生长度为5的随机中文汉字。
///     ${now:format}：now为固定字符串。format为时间格式化字符串。产生基于当前时间的格式化字符串。
/// 示例：${now:unix} 产生当前时间字符串，时间格式为unix时间戳，单位为秒。
///     ${autoIncrement@identify@start@max@resetType@padding}：autoIncrement为固定字符串。identify为自增序列的唯一标识，start为缺省起始值，max为最大数值，resetType为重置类型，固定格式为Y|M|D|H|m|s（即年月日时分秒），padding的值为true或者false,表示是否补0。 产生有记录的递增数字。
/// 示例：${autoIncrement@0@1000@H@true} 产生有记录的自增序号，从0开始，最大值为1000，每小时自动重置为0，并且前置补0对齐，对齐长度为4（即MAX的位数：1000）。
/// </remarks>
public class DefaultSerialNumberGenerator: ISingletonDependency
{
    private const string Pattern = @"\${(.*?)}";
    private readonly IAutoIncrementedNumberGenerator _autoIncrementedNumberGenerator;
    private readonly IClock _clock;

    public DefaultSerialNumberGenerator(IAutoIncrementedNumberGenerator autoIncrementedNumberGenerator, IClock clock)
    {
        _autoIncrementedNumberGenerator = autoIncrementedNumberGenerator;
        _clock = clock;
    }

    public async ValueTask<List<string>> PreCreateManyByExpressionAsync(string expression, int count)
    {
        var matches = Regex.Matches(expression, Pattern);
        var numbers = Enumerable.Repeat(expression, count).ToList();
        foreach (Match match in matches)
        {
            var exp = match.ToString().Replace("${","").Replace("}","").Split("@");
            switch (exp[0])
            {
                case "number":
                    if (exp[1] != "rand" && int.TryParse(exp[1], out int start) && int.TryParse(exp[2], out int end) && Boolean.TryParse(exp[3], out bool padding))
                    {
                        for (var i = 0; i < count; i++)
                        {
                            var index = i.ToString();
                            if (padding)
                            {
                                index = index.PadLeft((count - start + 1).ToString().Length, '0');
                            }
                            numbers[i] = numbers[i].Replace(match.ToString(), index);
                        }
                    }

                    break;
                case "autoIncrement": // ${autoIncrement@identify@start@max@resetType@padding}
                    exp = match.ToString().Replace("${","").Replace("}","").Split("@");
                    if (int.TryParse(exp[2], out start) && int.TryParse(exp[3], out int max) &&
                        Boolean.TryParse(exp[5], out padding))
                    {
                        var ts = TimeSpan.Zero;
                        if (exp[4] == "Y") ts = TimeSpan.FromSeconds((new DateTime(_clock.Now.AddYears(1).Year, 1, 1, 0, 0, 0) - _clock.Now).TotalMilliseconds);
                        if (exp[4] == "M") ts = TimeSpan.FromSeconds((new DateTime(_clock.Now.AddMonths(1).Year, _clock.Now.AddMonths(1).Month, 1, 0, 0, 0) - _clock.Now).TotalMilliseconds);
                        if (exp[4] == "D") ts = TimeSpan.FromSeconds((new DateTime(_clock.Now.AddDays(1).Year, _clock.Now.AddDays(1).Month, _clock.Now.AddDays(1).Day, 0, 0, 0) - _clock.Now).TotalMilliseconds);
                        if (exp[4] == "H") ts = TimeSpan.FromSeconds((new DateTime(_clock.Now.AddHours(1).Year, _clock.Now.AddHours(1).Month, _clock.Now.AddHours(1).Day, _clock.Now.AddHours(1).Hour, 0, 0) - _clock.Now).TotalMilliseconds);
                        if (exp[4] == "m") ts = TimeSpan.FromSeconds((new DateTime(_clock.Now.AddMinutes(1).Year, _clock.Now.AddMinutes(1).Month, _clock.Now.AddMinutes(1).Day, _clock.Now.AddMinutes(1).Hour, _clock.Now.AddMinutes(1).Minute, 0) - _clock.Now).TotalMilliseconds);
                        if (exp[4] == "s") ts = TimeSpan.FromSeconds((new DateTime(_clock.Now.AddSeconds(1).Year, _clock.Now.AddSeconds(1).Month, _clock.Now.AddSeconds(1).Day, _clock.Now.AddSeconds(1).Hour, _clock.Now.AddSeconds(1).Minute, _clock.Now.AddSeconds(1).Second) - _clock.Now).TotalMilliseconds);
                        for (var i = 0; i < count; i++)
                        {
                            var expiredAt = new DateTimeOffset(_clock.Now.AddMilliseconds(ts.Milliseconds))
                                .ToUnixTimeMilliseconds();
                            var autoIncrementNumber =
                                await _autoIncrementedNumberGenerator.GetAsync(exp[1], start, expiredAt);
                            if (autoIncrementNumber > max) throw new BusinessException($"{exp[1]}的自增序号超出最大值{max}");
                            var index = autoIncrementNumber.ToString();
                            if (padding)
                            {
                                index = index.PadLeft((count - start + 1).ToString().Length, '0');
                            }
                            numbers[i] = numbers[i].Replace(match.ToString(), index);
                            await Task.Delay(TimeSpan.FromSeconds(0.5));
                        }
                    }
                    break;
            }
        }

        return numbers;
    }
}